<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 
<html>
<head>
<title>Nibbles in Java Gnome</title>
<link rel="stylesheet" href="/cfg/format.css" type="text/css">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="keywords" content="GUI, Java Gnome, Cairo, drawing, Java, 
programming, Linux, game, Nibbles">
<meta name="description" content="Nibbles in Java Gnome">
<meta name="language" content="en">
<meta name="author" content="Jan Bodnar">
<meta name="distribution" content="global">

<script type="text/javascript" src="/lib/jquery.js"></script>
<script type="text/javascript" src="/lib/common.js"></script>

</head>

<body>

<div class="container">

<div id="wide_ad" class="ltow">
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* 160x600, August 2011 */
google_ad_slot = "2484182563";
google_ad_width = 160;
google_ad_height = 600;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div>

<div class="content">


<a href="/" title="Home">Home</a>&nbsp;
<a href="..">Contents</a>


<h1>Nibbles in Java Gnome</h1>


<p>
In this part of the Java Gnome programming tutorial, we will create a Nibbles
game clone.
</p>

<div class="center">
<script type="text/javascript"><!--
google_ad_client = "ca-pub-9706709751191532";
/* top_horizontal */
google_ad_slot = "3327173442";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div>


<h2>Nibbles</h2>

<p>
<b>Nibbles</b> is an older classic video game. It was first created in late 70s. 
Later it was brought to PCs. In this game the player
controls a snake. The objective is to eat as many apples as possible. 
Each time the snake eats an apple, its body grows. The snake must avoid the 
walls and its own body. 
</p>


<h2>Development</h2>

<p>
The size of each of the joints of a snake is 10px. The snake is controlled 
with the cursor keys. Initially, the snake has three joints. The game
starts immediately. When the game is finished, we display "Game Over"
message in the statusbar widget. 
</p>

<div class="codehead">board.java</div>
<pre class="code">
package com.zetcode;

import java.io.FileNotFoundException;

import java.util.Timer;
import java.util.TimerTask;

import org.freedesktop.cairo.Context;

import org.gnome.gdk.Color;
import org.gnome.gdk.EventExpose;
import org.gnome.gdk.EventKey;
import org.gnome.gdk.Keyval;
import org.gnome.gdk.ModifierType;
import org.gnome.gdk.Pixbuf;
import org.gnome.gtk.DrawingArea;
import org.gnome.gtk.Justification;
import org.gnome.gtk.Label;
import org.gnome.gtk.StateType;
import org.gnome.gtk.Widget;


public class Board extends DrawingArea implements Widget.ExposeEvent {

    private final int WIDTH = 300;
    private final int HEIGHT = 300;
    private final int DOT_SIZE = 10;
    private final int ALL_DOTS = 900;
    private final int RAND_POS = 29;
    private final int DELAY = 140;
    private final int PERIOD = 80;

    private int x[] = new int[ALL_DOTS];
    private int y[] = new int[ALL_DOTS];

    private int dots;
    private int apple_x;
    private int apple_y;

    private boolean left = false;
    private boolean right = true;
    private boolean up = false;
    private boolean down = false;
    private boolean inGame = true;

    private Timer timer;

    private Pixbuf dot;
    private Pixbuf apple;
    private Pixbuf head;
    
    private Label statusbar;

    public Board(Label statusbar) {
        
        this.statusbar = statusbar;

        connect(new SnakeKeyListener());

        modifyBackground(StateType.NORMAL, Color.BLACK);

        try {
            dot = new Pixbuf("dot.png");
            apple = new Pixbuf("apple.png");
            head = new Pixbuf("head.png");

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        connect(this);
        setCanFocus(true);

        initGame();
    }
    
    
    public Timer getTimer() { return timer; }


    public void initGame() {

        dots = 3;

        for (int z = 0; z < dots; z++) {
            x[z] = 50 - z * 10;
            y[z] = 50;
        }

        locateApple();

        timer = new Timer();
        timer.scheduleAtFixedRate(new ScheduleTask(), DELAY, PERIOD);

    }


    public void drawObjects(Context cr) {


        if (inGame) {

            cr.setSource(apple, apple_x, apple_y);
            cr.paint();

            for (int z = 0; z < dots; z++) {
                if (z == 0) {
                    cr.setSource(head, x[z], y[z]);
                    cr.paint();
                } else {
                    cr.setSource(dot, x[z], y[z]);
                    cr.paint();
                }
            }

        } else {
            gameOver();
        }
    }


    public void gameOver() {
        
        timer.cancel();

        statusbar.setJustify(Justification.LEFT);
        statusbar.setAlignment(0f, 0.5f);
        statusbar.setLabel("Game Over");
    }


    public void checkApple() {

        if ((x[0] == apple_x) &amp;&amp; (y[0] == apple_y)) {
            dots++;
            locateApple();
        }
    }

    public void move() {

        for (int z = dots; z > 0; z--) {
            x[z] = x[(z - 1)];
            y[z] = y[(z - 1)];
        }

        if (left) {
            x[0] -= DOT_SIZE;
        }

        if (right) {
            x[0] += DOT_SIZE;
        }

        if (up) {
            y[0] -= DOT_SIZE;
        }

        if (down) {
            y[0] += DOT_SIZE;
        }
    }

    public void checkCollision() {

        for (int z = dots; z > 0; z--) {

            if ((z > 4) &amp;&amp; (x[0] == x[z]) &amp;&amp; (y[0] == y[z])) {
                inGame = false;
            }
        }

        if (y[0] > HEIGHT) {
            inGame = false;
        }

        if (y[0] < 0) {
            inGame = false;
        }

        if (x[0] > WIDTH) {
            inGame = false;
        }

        if (x[0] < 0) {
            inGame = false;
        }
    }

    public void locateApple() {
        int r = (int) (Math.random() * RAND_POS);
        apple_x = ((r * DOT_SIZE));
        r = (int) (Math.random() * RAND_POS);
        apple_y = ((r * DOT_SIZE));
    }

    public boolean onExposeEvent(Widget widget, EventExpose eventExpose) {
    
        Context cr = new Context(widget.getWindow());
        drawObjects(cr);
        
        return false;
    }


    class ScheduleTask extends TimerTask {

        public void run() {

            if (inGame) {
                checkApple();
                checkCollision();
                move();
            }
            queueDraw();
        }
    }

    class SnakeKeyListener implements Widget.KeyPressEvent {

        public boolean onKeyPressEvent(Widget widget, EventKey eventKey) {
            
            final Keyval key;
            final ModifierType mod;
            
            key = eventKey.getKeyval();
            mod = eventKey.getState();

            if ((key == key.Left) &amp;&amp; (!right)) {
                left = true;
                up = false;
                down = false;
            }

            if ((key == key.Right) &amp;&amp; (!left)) {
                right = true;
                up = false;
                down = false;
            }

            if ((key == key.Up) &amp;&amp; (!down)) {
                up = true;
                right = false;
                left = false;
            }

            if ((key == key.Down) &amp;&amp; (!up)) {
                down = true;
                right = false;
                left = false;
            }
            
            return false;
        }
    }
}
</pre>


<p>
First we will define some globals used in our game. 
</p>

<p>
The <b class="keyword">WIDTH</b> and <b class="keyword">HEIGHT</b> constants determine 
the size of the Board. The <b class="keyword">DOT_SIZE</b> is the size of the apple and the dot
of the snake. The <b class="keyword">ALL_DOTS</b> constant defines the maximum number of 
possible dots on the Board.
The <b class="keyword">RAND_POS</b> constant is used to calculate a random position of an apple. 
The <b class="keyword">DELAY</b> constant determines the speed of the game.
</p>

<pre class="explanation">
 private int x[] = new int[ALL_DOTS];
 private int y[] = new int[ALL_DOTS];
</pre>

<p>
These two arrays store x, y coordinates of all possible joints of a snake. 
</p>

<p>
The <b class="keyword">initGame()</b> method initializes variables, loads 
images and starts a timeout function.
</p>

<p>
In the <b class="keyword">move()</b> method we have the key algorithm of the game. 
To understand it, look at how the snake is moving. You control the head of the snake. 
You can change its direction with the cursor keys. The rest of the joints move
one position up the chain. The second joint moves where the first was, 
the third joint where the second was etc. 
</p>

<pre class="explanation">
 for (int z = dots; z > 0; z--) {
     x[z] = x[(z - 1)];
     y[z] = y[(z - 1)];
 }
</pre>

<p>
This code moves the joints up the chain.
</p>

<pre class="explanation">
 if (left) {
     x[0] -= DOT_SIZE;
 }
</pre>

<p>
Move the head to the left.
</p>

<p>
In the <b class="keyword">checkCollision()</b> method, we determine if 
the snake has hit itself or one of the walls. 
</p>

<pre class="explanation">
 for (int z = dots; z > 0; z--) {
     if ((z > 4) &amp;&amp; (x[0] == x[z]) &amp;&amp; (y[0] == y[z])) {
         inGame = false;
     }
 }
</pre>

<p>
Finish the game, if the snake hits one of its joints with the head. 
</p>

<pre class="explanation">
 if (y[0] > HEIGHT) {
     inGame = false;
 }
</pre>

<p>
Finish the game, if the snake hits the bottom of the Board. 
</p>


<p>
The <b class="keyword">locateApple()</b> method locates an apple randomly 
on the form.
</p>

<pre class="explanation">
 int r = (int) (Math.random() * RAND_POS);
</pre>

<p>
We get a random number from 0 to RAND_POS - 1.
</p>

<pre class="explanation">
 apple_x = ((r * DOT_SIZE));
 ...
 apple_y = ((r * DOT_SIZE));
</pre>

<p>
These line set the x, y coordinates of the apple
object. 
</p>

<p>
In the <b class="keyword">onKeyPressEvent()</b> method of the Board class, we 
deternime which keys the player hit.
</p>

<pre class="explanation">
 if ((key == key.Left) &amp;&amp; (!right)) {
     left = true;
     up = false;
     down = false;
 }
</pre>

<p>
If we hit the left cursor key, we set <b class="keyword">left</b> variable to 
true. This variable is used in the <b class="keyword">move()</b>
method to change coordinates of the snake object. Notice also, that
when the snake is heading to the right, we cannot turn immediately 
to the left. 
</p>

<div class="codehead">nibbles.java</div>
<pre class="code">
package com.zetcode;

import java.util.Timer;

import org.gnome.gdk.Event;
import org.gnome.gtk.Gtk;
import org.gnome.gtk.Label;
import org.gnome.gtk.VBox;
import org.gnome.gtk.Widget;
import org.gnome.gtk.Window;
import org.gnome.gtk.WindowPosition;


/**
 * ZetCode Java Gnome tutorial
 *
 * This program creates a Nibbles game clone.
 *
 * @author jan bodnar
 * website zetcode.com
 * last modified March 2009
 */

public class GNibbles extends Window {

    Board board;
    Label statusbar;

    public GNibbles() {
    
        setTitle("Nibbles");
        
        initUI();
        
        setDefaultSize(320, 320);
        setPosition(WindowPosition.CENTER);
        
        showAll();
    }
    
    public void initUI() {
     
        VBox vbox = new VBox(false, 0);
     
        statusbar = new Label("");
        board = new Board(statusbar);
        
        vbox.packStart(board);
        vbox.packStart(statusbar, false, false, 0);
        
        add(vbox);
        
        connect(new Window.DeleteEvent() {
            public boolean onDeleteEvent(Widget source, Event event) {
                Timer timer = board.getTimer();
                timer.cancel();
                Gtk.mainQuit();
                return false;
            }
        });
    }

    public static void main(String[] args)  {
        Gtk.init(args);
        new GNibbles();        
        Gtk.main();
    }
}
</pre>

<p>
In this class, we set up the Nibbles game. Notice that we get
the timer object from the board. This is to perform a clean exit. 
</p>

<br>
<img src="/img/gui/javagnome/nibbles.png" alt="Nibbles">
<div class="figure">Figure: Nibbles</div>

<hr class="btm">

<p>
This was the Nibbles computer game programmed using the Java Gnome programming library.
</p>

<br>
<div class="center"> 
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* horizontal */
google_ad_slot = "1734478269";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script> 
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> 
</script> 
</div> 
<br>



<div class="botNav, center">
<span class="botNavItem"><a href="/">Home</a></span> ‡ <span class="botNavItem"><a href="..">Contents</a></span> ‡ 
<span class="botNavItem"><a href="#">Top of Page</a></span>
</div>


<div class="footer">
<div class="signature">
<a href="/">ZetCode</a> last modified March 12, 2009  <span class="copyright">&copy; 2007 - 2012 Jan Bodnar</span>
</div>
</div>

</div> <!-- content -->

</div> <!-- container -->

</body>
</html>

